﻿using _80X_CG_Frame.Interfaces;
using _80X_CG_Frame.Protocols;
using _80X_CG_Frame.Utils;
using _80X_CG_Frame.Workers;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace _80X_CG_Frame.Objects
{
    public class PLC : IPLC, INotifyPropertyChanged
    {
        //private object plc_write_lock = new object();
        //private object plc_read_lock = new object();
        private L02_MC_Class plc = new L02_MC_Class();
        //private Dictionary<int, string> dic_warn;
        private JToken dic_warn;
        private bool isConnect;
        private string Memories;

        public bool IsConnect
        {
            get
            {
                if (isConnect != plc.IsConnect())
                {
                    isConnect = plc.IsConnect();
                    NotifyPropertyChanged();
                }
                return isConnect;
            }
            set => isConnect = value;
        }

        public PLC()
        {
            IsConnect = false;
            Memories = "";

            dic_warn = GLOBAL.config.configs["plc告警信息"];

        }

        public async Task<bool> ConnectAsync(string ip, int port, int times = 5)
        {
            if (!IsConnect)
            {
                plc.Close();
                bool connected = await plc.ConnectServer(ip, port, times);
                NotifyPropertyChanged("IsConnect");
                RefreshMBit();
                return connected;
            }
            else
            {
                NotifyPropertyChanged("IsConnect");
                return false;
            }
        }
        public async Task<bool> ConnectAsync(string address, int times = 5)
        {
            if (!IsConnect)
            {
                string[] addresses = address.Split(':');
                if (addresses.Length != 2)
                {
                    throw new FormatException("地址格式不正确");
                }
                else
                {
                    plc.Close();
                    bool connected = await plc.ConnectServer(
                        addresses[0],
                        Convert.ToInt32(addresses[1]),
                        times);
                    NotifyPropertyChanged("IsConnect");
                    RefreshMBit();
                    return connected;
                }
            }
            else
            {
                NotifyPropertyChanged("IsConnect");
                return false;
            }
        }
        public bool Connect(string address, int times = 5)
        {
            if (!IsConnect)
            {
                string[] addresses = address.Split(':');
                if (addresses.Length != 2)
                {
                    throw new FormatException("地址格式不正确");
                }
                else
                {
                    bool connected = plc.ConnectServer(
                        addresses[0],
                        Convert.ToInt32(addresses[1]));
                    NotifyPropertyChanged("IsConnect");
                    RefreshMBit();
                    return connected;
                }
            }
            else
            {
                NotifyPropertyChanged("IsConnect");
                return false;
            }
        }

        public bool Disconnect()
        {
            if (plc.Close())
            {
                NotifyPropertyChanged("IsConnect");
                return true;
            }
            NotifyPropertyChanged("IsConnect");
            return false;
        }
        /*
        public bool IsConnect()
        {
            return plc.IsConnect();
        }
        */
        /// <summary>
        /// 读双字
        /// </summary>
        /// <param name="dname">寄存器名</param>
        /// <param name="readDwordData">读取的数据</param>
        /// <returns></returns>
        public bool ReadDword(string dname, ref int readDwordData)
        {
            if (plc.BinReadDword(dname, ref readDwordData))
                return true;
            return false;
        }
        /// <summary>
        /// 读双字
        /// </summary>
        /// <param name="dname">寄存器名</param>
        /// <param name="readDwordData">读取的数据(浮点)</param>
        /// <returns></returns>
        public bool ReadDword(string dname, ref float readDwordData)
        {
            if (plc.BinReadDword(dname, ref readDwordData))
                return true;
            return false;
        }
        /// <summary>
        /// 写双字
        /// </summary>
        /// <param name="dname">寄存器名</param>
        /// <param name="writeDwordData">写入的数据</param>
        /// <returns></returns>
        public bool WriteDword(string dname, int writeDwordData, double k = 10000)
        {
            if (plc.BinWriteDword(dname, writeDwordData))
                return true;
            return false;

        }
        /// <summary>
        /// 写双字
        /// </summary>
        /// <param name="dname">寄存器名</param>
        /// <param name="writeDwordData">写入的数据(浮点)</param>
        /// <returns></returns>
        public bool WriteDword(string dname, float writeDwordData)
        {
            if (plc.BinWriteDword(dname, writeDwordData))
                return true;
            return false;

        }

        /// <summary>
        /// 读位
        /// </summary>
        /// <param name="mname"></param>
        /// <param name="readMbitdData"></param>
        /// <returns></returns>
        public bool ReadMBit(string mname, ref string readMbitdData)
        {
            /*
            if (plc.BinReadMbit(mname, ref readMbitdData, "1"))
                return true;
            return false;
            */
            if (Memories != "")
            {
                readMbitdData = "" + Memories[Convert.ToInt32(mname)];
                return true;
            }
            return false;

        }
        /// <summary>
        /// 写位
        /// </summary>
        /// <param name="mname"></param>
        /// <param name="writeMbitdData"></param>
        /// <returns></returns>
        public bool WriteMBit(string mname, string writeMbitdData)
        {
            if (plc.BinWriteMbit(mname, writeMbitdData))
                return true;
            return false;

        }

        /// <summary>
        /// 刷新M值
        /// </summary>
        /// <returns></returns>
        public bool RefreshMBit()
        {
            try
            {
                return plc.BinReadMbit("0", ref Memories, "2048");
            }
            catch (System.IO.IOException e)
            {
                Console.WriteLine(e.ToString());
                return false;
            }
            catch (ObjectDisposedException e)
            {
                Console.WriteLine(e.ToString());
                return false;
            }
        }
        public string[] ReadMWarning()
        {
            List<string> result = new List<string>();
            string str = "";
            string temp = "";
            try
            {
                plc.BinReadMbit("7000", ref str, "1005");
                for (int i = 0; i < str.Length; i++)
                {
                    //temp = str.Substring(i, 1);
                    int index = str.IndexOf('1', i);
                    if (index == -1)
                        break;
                    else
                    {
                        i = index;
                        string memory_index = (7000 + index).ToString();
                        var t_jtoken = dic_warn[memory_index];
                        if (t_jtoken != null)
                            result.Add((string)t_jtoken);
                    }
                    /*
                    if (temp == "1")
                    {
                        result.Add(dic_warn[i]);
                    }
                    */
                }

            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            return result.ToArray();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        // This method is called by the Set accessor of each property.
        // The CallerMemberName attribute that is applied to the optional propertyName
        // parameter causes the property name of the caller to be substituted as an argument.
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    public class OMRON : AsyncClient, IPLC, INotifyPropertyChanged
    {
        //public bool IsConnect => throw new NotImplementedException();
        OMRON_PLC plc;
        object tcp_lock = new object();
        static int buffer_size = 4096;

        private JToken dic_warn;
        private List<string> Memories;
        public OMRON() : base()
        {
            Memories = new List<string>();
            dic_warn = GLOBAL.config.configs["plc告警信息"];
        }

        public new async Task<bool> ConnectAsync(string ip, int port, int times = 5)
        {
            if (!IsConnect)
            {

                bool connected = false;
                try
                {
                    DisConnect();
                    client = new TcpClient();
                    client.ReceiveTimeout = 1000;
                    client.SendTimeout = 1000;
                    connected = await TcpAsyncConnect.ConnectAsync(client, ip, port, times);

                    if (IsConnect)
                    {
                        ns = client.GetStream();
                        ns.ReadTimeout = 1000;
                        ns.WriteTimeout = 1000;
                    }

                    int pc_node =
                        int.Parse(
                        ((IPEndPoint)client.Client.LocalEndPoint)
                        .Address
                        .ToString()
                        .Split('.')[3]);

                    int plc_node =
                        int.Parse(
                        ((IPEndPoint)client.Client.RemoteEndPoint)
                        .Address
                        .ToString()
                        .Split('.')[3]);
                    plc = new OMRON_PLC(plc_node, pc_node);
                    var t = await ShakeHandAsync();
                    if (t == true)
                    {
                        RefreshMBit();
                        connected = true;
                    }
                }
                catch (Exception e)
                {
                    //connected = false;
                    Console.WriteLine("PLC连接失败");
                    Console.WriteLine(e.ToString());
                }
                return connected;
            }
            else
            {
                return false;
            }
        }
        public new async Task<bool> ConnectAsync(string address, int times = 5)
        {

            if (!IsConnect)
            {
                string[] addresses = address.Split(':');
                if (addresses.Length != 2)
                {
                    throw new FormatException("地址格式不正确");
                }
                else
                {
                    return await ConnectAsync(addresses[0], Convert.ToInt32(addresses[1]), times);
                }
            }
            else
            {
                return false;
            }
        }
        public bool Connect(string address, int times = 5)
        {
            if (!IsConnect)
            {
                string[] addresses = address.Split(':');
                if (addresses.Length != 2)
                {
                    throw new FormatException("地址格式不正确");
                }
                else
                {
                    bool connected = false;
                    try
                    {
                        DisConnect();
                        client = new TcpClient();
                        client.ReceiveTimeout = 1000;
                        client.SendTimeout = 1000;
                        client.Connect(addresses[0], Convert.ToInt32(addresses[1]));

                        if (IsConnect)
                        {
                            ns = client.GetStream();
                            ns.ReadTimeout = 1000;
                            ns.WriteTimeout = 1000;
                        }

                        int pc_node =
                            int.Parse(
                            ((IPEndPoint)client.Client.LocalEndPoint)
                            .Address
                            .ToString()
                            .Split('.')[3]);

                        int plc_node =
                            int.Parse(
                            ((IPEndPoint)client.Client.RemoteEndPoint)
                            .Address
                            .ToString()
                            .Split('.')[3]);
                        plc = new OMRON_PLC(plc_node, pc_node);
                        var t = ShakeHandAsync();
                        t.Wait();
                        if (t.Result == true)
                        {
                            RefreshMBit();
                            connected = true;
                        }
                    }
                    catch (Exception e)
                    {
                        //connected = false;
                        Console.WriteLine("PLC连接失败");
                        Console.WriteLine(e.ToString());
                    }
                    return connected;
                }
            }
            else
            {
                return false;
            }
        }

        private bool Send(string msg)
        {
            try
            {
                if (ns.CanWrite)
                {
                    //byte[] buffer = Encoding.ASCII.GetBytes(msg);
                    byte[] buffer = String2bytes(msg);// + "\r\n");
                    lock (tcp_lock)
                    {
                        ns.Write(buffer, 0, buffer.Length);
                    }
                }
                else
                    return false;

            }
            catch (ObjectDisposedException e)
            {
                Console.WriteLine(e.ToString());
                return false;
            }
            catch (IOException e)
            {

                DisConnect();
                Console.WriteLine(e.ToString());
                return false;
            }
            return true;
        }

        private string Read()
        {
            string result = "";
            try
            {
                if (ns.CanRead)// && ns.DataAvailable)
                {
                    byte[] buffer = new byte[buffer_size];

                    lock (tcp_lock)
                    {
                        int len = ns.Read(buffer, 0, buffer_size);
                        if (len > 0)
                        {
                            result = string.Concat(buffer.Take(len).Select(b => b.ToString("X2")));
                        }
                    }
                }
            }
            catch (ObjectDisposedException e)
            {
                Console.WriteLine(e.ToString());
            }
            return result;
        }

        private byte[] String2bytes(string str)
        {
            int length = str.Length / 2;
            byte[] bytes = new byte[length];
            for (int i = 0; i < length; i++)
            {
                bytes[i] = (byte)Convert.ToInt32(str.Substring(i * 2, 2), 16);
            }

            return bytes;
        }
        public bool Disconnect()
        {
            DisConnect();
            return true;
        }

        public bool ReadDword(string dname, ref int readDwordData)
        {
            throw new NotImplementedException();
        }

        public bool ReadDword(string dname, ref float readDwordData)
        {

            bool result = false;
            if (IsConnect)
            {
                try
                {
                    string send = "";
                    int start = Convert.ToInt32(dname);
                    plc.ReadIOMemoryWord(OMRON_PLC.Memory.DM, start, 2, ref send);
                    Send(send);
                    string receive = Read();

                    var result_float = new List<float>();
                    string error = "";

                    if (!plc.DWordResultReturn(receive, ref error, ref result_float))
                    {
                        Console.WriteLine(error);
                    }
                    else
                    {
                        readDwordData = result_float[0];
                        Console.WriteLine("读取成功");
                        result = true;
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
            return result;
        }

        public bool ReadMBit(string mname, ref string readMbitdData)
        {
            //10.01
            bool result = false;
            string[] addresses = mname.Split('.');
            if (addresses.Length != 2)
            {
                throw new FormatException("地址格式不正确");
            }
            else
            {
                int start = Convert.ToInt32(addresses[0]);
                int offset = Convert.ToInt32(addresses[1]);
                readMbitdData = Memories[(start * 16) + offset].Substring(1, 1);
                result = true;
            }
            return false;
        }

        public string[] ReadMWarning()
        {
            List<string> result = new List<string>();
            if (IsConnect)
            {
                try
                {
                    string send = "";
                    plc.ReadIOMemoryBit(OMRON_PLC.Memory.WR, 20, 0, 160, ref send);
                    Send(send);
                    string receive = Read();

                    List<string> r = new List<string>();
                    string error = "";

                    if (!plc.BitResultReturn(receive, ref error, ref r))
                    {
                        Console.WriteLine(error);
                    }
                    else
                    {
                        //result = r;

                        for (int i = 0; i < r.Count; i++)
                        {
                            var index = r.IndexOf("01", i);
                            if (index == -1)
                                break;
                            else
                            {
                                i = index;
                                string first = (20 + (index / 16)).ToString();
                                string last = (index % 16).ToString().PadLeft(2, '0');
                                string memory_index = first + '.' + last;
                                //var t_jtoken = dic_warn[memory_index];
                                //if (t_jtoken != null)
                                //    result.Add((string)t_jtoken);
                            }
                        }
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }

            return result.ToArray();
        }

        public bool RefreshMBit()
        {
            bool result = false;
            if (IsConnect)
            {
                try
                {
                    string send = "";
                    plc.ReadIOMemoryBit(OMRON_PLC.Memory.WR, 0, 0, 1600, ref send);
                    Send(send);
                    string receive = Read();

                    List<string> r = new List<string>();
                    string error = "";

                    if (!plc.BitResultReturn(receive, ref error, ref r))
                    {
                        Console.WriteLine(error);
                    }
                    else
                    {
                        Memories = r;
                        //WriteMsg("写入成功");
                        result = true;
                    }
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }
            }
            return result;
        }

        public bool WriteDword(string dname, int writeDwordData, double k = 10000)
        {

            bool result = false;
            string message = "";
            string error = "";
            int start = Convert.ToInt32(dname);
            List<float> data = new List<float>();
            data.Add((float)(writeDwordData / k));
            plc.WriteIOMemoryDWord(OMRON_PLC.Memory.DM, start, data, ref message);
            //Console.WriteLine(message);
            try
            {
                Send(message);
                message = Read();
            }
            catch (IOException e)
            {
                result = false;
                Console.WriteLine(e.ToString());
            }
            return result;
            //bool result = false;
            //string message = "";
            //string error = "";
            //int start = Convert.ToInt32(dname);
            //List<string> data = new List<string>();
            //data.Add(writeDwordData.ToString());
            //plc.WriteIOMemoryWord(OMRON_PLC.Memory.DM, start, data, ref message);
            //Console.WriteLine(message);
            //try
            //{
            //    Send(message);
            //    message = Read();
            //}
            //catch (IOException e)
            //{
            //    result = false;
            //}
            //result = true;
            //return result;
            //throw new NotImplementedException();
        }

        public bool WriteDword(string dname, float writeDwordData)
        {
            bool result = false;
            string message = "";
            string error = "";
            int start = Convert.ToInt32(dname);
            List<float> data = new List<float>();
            data.Add(writeDwordData);
            plc.WriteIOMemoryDWord(OMRON_PLC.Memory.DM, start, data, ref message);
            //Console.WriteLine(message);
            try
            {
                Send(message);
                message = Read();
            }
            catch (IOException e)
            {
                result = false;
            }
            result = true;
            return result;
            //throw new NotImplementedException();
        }

        public bool WriteMBit(string mname, string writeMbitdData)
        {
            bool result = false;
            string[] addresses = mname.Split('.');
            if (addresses.Length != 2)
            {
                throw new FormatException("地址格式不正确");
            }
            else
            {
                string message = "";
                string error = "";
                int start = Convert.ToInt32(addresses[0]);
                int offset = Convert.ToInt32(addresses[1]);
                List<string> data = new List<string>();
                data.Add(writeMbitdData);
                plc.WriteIOMemoryBit(OMRON_PLC.Memory.WR, start, offset, data, ref message);

                //Console.WriteLine(message);

                try
                {
                    Send(message);
                    message = Read();
                }
                catch (IOException e)
                {
                    result = false;
                }
                result = true;
                return result;
            }
        }
        private async Task<bool> ShakeHandAsync()
        {
            bool result = false;
            if (IsConnect)
            {
                try
                {
                    string message = "";
                    string error = "";
                    plc.ShakeHand(ref message);
                    //Console.WriteLine(message);
                    bool success = await Task.Run(() =>
                    {
                        try
                        {
                            Send(message);
                            message = Read();
                        }
                        catch (IOException e)
                        {
                            //WriteMsg(e.Message);
                            return false;
                        }
                        return true;
                    });
                    //Console.WriteLine(message);

                    if (!success)
                    {
                        error = "通信失败";
                        result = false;
                    }
                    else if (!plc.NormalReturn(message, ref error))
                    {
                        result = false;
                    }
                    else
                    {
                        result = true;
                    }
                }
                catch (Exception e)
                {
                    result = false;
                    //WriteMsg(e.Message);
                }
            }
            return result;
        }

    }

}
